#include "wlterm_parse.h"
#include "ctype.h"
#include <string.h>

WltermData* get_previous_data(WltermSession *session) {
	gint pre_x, pre_y;
	if (session->y) {
		pre_x = session->x;
		pre_y = session->y - 1;
	} else {
		if (session->x) {
			pre_y = session->site->cols - 1;
			pre_x = session->x - 1;
		} else {
			pre_x = pre_y = 0;
		}
	}
	return &session->screen[pre_x][pre_y];
}

void parse_putchar(WltermSession *session, gchar ch)
{
	WltermSite *site = session->site;
	WltermData *data = &session->screen[session->x][session->y];
	WltermData *predata = get_previous_data(session);
	if (!flag_is(data->flag_m, FLAG_M_MASK)) {
		memmove(data, predata, sizeof(WltermData));
	} else {
		if (!flag_is(data->flag_m, FLAG_BG_M))
			data->bg = predata->bg;
		if (!flag_is(data->flag_m, FLAG_FG_M))
			data->fg = predata->fg;
		~(data->flag_m) & predata->flag | data->flag;
	}
	data->ch = ch;
	if (session->y >= (site->cols - 1)) { /* FIXME */
		session->y = 0;
		if (session->x >= (site->rows - 1)) {
			session->x = 0; /* end to the bottom , do what? */
		} else {
			session->x++; /* end to the bottom , do what? */
		}
	} else {
		session->y++;
	}
}

void ansi_erase_line(WltermSession *session, gint p)
{
	WltermSite *site = session->site;
	gint i;
	switch (p) {
		case 0: /* clear from current position to end */
			for (i = session->y; i < site->cols; i++) {
				session->screen[session->x][i].ch = '\0';
				//flag_set(&(session->screen[session->x][i].flag), FLAG_UPDATE, 1);
			}
			break;
		case 1: /* clear from head to current position */
			for (i = 0; i < session->y+1; i++) {
				session->screen[session->x][i].ch = '\0';
				//flag_set(&(session->screen[session->x][i].flag), FLAG_UPDATE, 1);
			}
			break;
		case 2:
		default: /* clear whole line */
			for (i = 0; i < site->cols; i++) {
				session->screen[session->x][i].ch = '\0';
				//flag_set(&(session->screen[session->x][i].flag), FLAG_UPDATE, 1);
				/* FIXME clear the flag? */
			}
			break;
	}
}

void ansi_goto_xy(WltermSession *session, gint p1, gint p2)
{
	WltermSite *site = session->site;
	if (p1 < 0) 
		p1 = 0;
	else if (p1 >= site->rows)
		p1 = site->rows - 1;
	if (p2 < 0) 
		p2 = 0;
	else if (p2 >= site->cols)
		p2 = site->cols - 1;
	session->x = p1;
	session->y = p2;
}

void ansi_clear_screen(WltermSession *session, gint p)
{
	WltermSite *site = session->site;
	gint i, j;
	switch (p) {
		case 1: /* clear from top to current line */
			for (i = 0; i <= session->x; i++) {
				for (j = 0; j <= site->cols; j++) {
					session->screen[i][j].ch = '\0';
					//flag_set(&(session->screen[i][j].flag), FLAG_UPDATE, 1);
			/* FIXME set flag to 0 ? */
				}
			}
			break;
		case 2: /* clear the whole */
			for (i = 0; i <= site->rows; i++) {
				for (j = 0; j <= site->cols; j++) {
					session->screen[i][j].ch = '\0';
					//flag_set(&(session->screen[i][j].flag), FLAG_UPDATE, 1);
			/* FIXME set flag to 0 ? */
				}
			}
			session->x = 0;
			session->y = 0;
			break;
		case 0:
		default: /* erase from current line to bottom */
			for (i = session->x; i < site->rows; i++) {
				for (j = 0; j <= site->cols; j++) {
					session->screen[i][j].ch = '\0';
					//flag_set(&(session->screen[i][j].flag), FLAG_UPDATE, 1);
			/* FIXME set flag to 0 ? */
				}
			}
			break;
	}
}

void parse_color(WltermSession *session, const gchar *p)
{
	WltermData *data = &session->screen[session->x][session->y];
	/* set the data->flag = predata->flag FIXME*/
	data->flag = get_previous_data(session)->flag;
	while (*p) {
		int param = 0;
		while (isdigit(*p)) {
			param *= 10;
			param += *p - '0';
			p++;
		}
		if (param < 30) {
			switch (param) {
				case 0:
					/* FIXME */
					data->fg = 7;
					data->bg = 0;
					flag_set(&data->flag_m, FLAG_BG_M, 1);
					flag_set(&data->flag_m, FLAG_FG_M, 1);
					flag_set(&data->flag_m, FLAG_BRIGHT_M, 1);
					flag_set(&data->flag, FLAG_BRIGHT, 0);
					break;
				case 1:
					flag_set(&data->flag_m, FLAG_BRIGHT_M, 1);
					flag_set(&data->flag, FLAG_BRIGHT, 1);
					break;
				case 4:		
					flag_set(&data->flag_m, FLAG_UNDERLINE_M, 1);
					flag_set(&data->flag, FLAG_UNDERLINE, 1);
					break;
				case 5:		
				case 6:
					flag_set(&data->flag_m, FLAG_BLINK_M, 1);
					flag_set(&data->flag, FLAG_BLINK, 1);
					break;
				case 7:		
					flag_set(&data->flag_m, FLAG_INVERSE_M, 1);
					flag_set(&data->flag, FLAG_INVERSE, 1);
					break;
				case 8:		
					flag_set(&data->flag_m, FLAG_INVISIBLE_M, 1);
					flag_set(&data->flag, FLAG_INVISIBLE, 1);
					break;
			}
		} else {
			if (param >= 40 && param <= 47) {
				data->bg = param - 40;
				flag_set(&data->flag_m, FLAG_BG_M, 1);
			} else if (param <= 37) {
				data->fg = param - 30;
				flag_set(&data->flag_m, FLAG_FG_M, 1);
			}
		}
		p++;
	}
}

void parse_ansi(WltermSession *session, gchar ch)
{
	WltermSite *site = session->site;
	const gchar *p_param = &session->ctrl_buf[2];
	if (ch == 'm') { /* handle the color sequence */
		parse_color(session, p_param);
	} else {
		gint p1 = 0, p2 = 0;
		gint n;
		n = sscanf(p_param, "%d;%d", &p1, &p2);
		if (p1 < 0) p1 = 0;
		if (p2 < 0) p2 = 0;
		switch (ch) { /* steal from pcmanx @_@*/
			case 'K':	//	Clear Line
				ansi_erase_line(session, p1);
				break;
			case 'H':	// Set Caret Pos
			case 'f':
				ansi_goto_xy(session, p1-1, p2-1);
				break;
			case 'J':	// Clear Screen
				ansi_clear_screen(session, p1);
				break;
			case 'E':
				break;
			case 'L':
				break;
			case 'A':
				if(p1 <= 0)
					p1=1;
				ansi_goto_xy(session, session->x - p1, session->y);
				break;
			case 'B':
				if( p1<=0 )
					p1=1;
				ansi_goto_xy(session, session->x + p1, session->y);
				break;
			case 'C':
				if( p1<=0 )
					p1=1;
				ansi_goto_xy(session, session->x, session->y + p1);
				break;
			case 'D':
				if( p1<=0 )
					p1=1;
				ansi_goto_xy(session, session->x, session->y - p1);
				break;
			case 'r':	
				/* ?? meaning ? FIXME */
				break;
			case 's':	/* save cursor pos */
				break;
			case 'u':	/* restore cursor pos */
				break;
			case '@':	/* insert char */
				break;
			case 'M':	/* delete n line */
				break;
			case 'P':	/* delete char */
				break;
#if 0
			case 'U':	/* next n page */
				break;
			case 'V':	/* previous n page */
				break;
#endif
			case 'Z':	/* cursor back tab */
				break;
			case 'h':	/* set mode */
				break;
			case 'l':	/* reset mode */
				break;
			case 'n':	/* Device Status Report */
				break;
		}
	}
}

void parse_newline(WltermSession *session)
{
	WltermSite *site = session->site;
	if (session->x < site->rows - 1) {
		session->x++;
	}
		/*when >  FIXME */
}

void parse_return(WltermSession *session)
{
	session->y = 0;
}

void parse_back(WltermSession *session)
{
	if (session->y > 0)
		session->y--;
}

void parse_bell(WltermSession *session)
{
	/* nothing */
	;
}

void parse_tab(WltermSession *session)
{
	session->y += ((session->y/4)*4) + 4;
}

void wlterm_parse_data(WltermSession *session, gchar *data, gint len)
{
	WltermSite *site = session->site;
	gchar ch;
	gint i;
	for (i = 0; i < len; i++) {
		ch = data[i];
		if (ch < ' ') {
			switch (ch) {
				case '\x1b':
					session->ctrl_buf[0] = '\x1b';
					session->p_ctrl_buf = &session->ctrl_buf[1];
					break;
				case '\n':
					parse_newline(session);
					break;
				case '\r':
					parse_return(session);
					break;
				case '\b':
					parse_back(session);
					break;
				case '\a':
					parse_bell(session);
					break;
				case '\t':
					parse_tab(session);
					break;
				default:
					/* FIXME */
					parse_putchar(session, ch);
					break;
			}
		} else {
			switch (session->ctrl_buf[0]) {
				case '\0':
					parse_putchar(session, ch);
					break;
				case '\x1b': /* in the esc control sequence */
					if (session->p_ctrl_buf < session->ctrl_buf +
							sizeof(session->ctrl_buf)) {
						*session->p_ctrl_buf = ch;
						session->p_ctrl_buf++;
					}
					if (session->ctrl_buf[1] == '[') {
						if (ch < '@' || ch == '[' || ch > '~') 
							break;
					} else {
						/* not sequence */
						parse_putchar(session, session->p_ctrl_buf[0]);
						parse_putchar(session, session->p_ctrl_buf[1]);
						session->ctrl_buf[0] = '\0';
						break;
					}
					if (session->p_ctrl_buf < session->ctrl_buf +
							sizeof(session->ctrl_buf)) 
						*session->p_ctrl_buf = '\0';
					/* handle the ansi ctrl parse FIXME */
					parse_ansi(session, ch);
					/* FIXME handle the invalid chars ? */
					session->ctrl_buf[0] = '\0';
					session->p_ctrl_buf = session->ctrl_buf;
			}
		}
	}

}
